#include <SDL.h>
#include <cmath>
#include <sstream>
#include "model.h"


Model::~Model() { 
  // These deletions eliminate "still reachable"s in Valgrind  
  deleteBricks();
  deleteItems();
  deleteExplosions();
  deleteBalls();
  delete ball;
  delete player;
  delete mfactory;
  delete factory;
  delete sound;
  delete strategy;
  delete io;
  delete gamedata;
}

void Model::makeBricks() {
	for(int j = 1;j<9;j++)
	{
		bricks.push_back(new Sprite(Vector2f(80*j,0),Vector2f(0,0),factory->getFrame("redbrick","brick")));
		bricks.push_back(new Sprite(Vector2f(80*j,40),Vector2f(0,0),factory->getFrame("bluebrick","brick")));
		bricks.push_back(new Sprite(Vector2f(80*j,80),Vector2f(0,0),factory->getFrame("greenbrick","brick")));
		//bricks.push_back(new MultiSprite(Vector2f(80*j,120),Vector2f(0,0),mfactory->getFrame("gbrick","gbrick"),"gbrick"));
		//bricks.push_back(new MultiSprite(Vector2f(80*j,160),Vector2f(0,0),mfactory->getFrame("sbrick","sbrick"),"sbrick"));
		//items.push_back(new Item(Vector2f(80*j,300),Vector2f(0,-100),factory->getFrame("redbrick","brick")));
	}
}

void Model::deleteBricks(){
	std::list<Sprite*>::iterator it = bricks.begin();
	while(it != bricks.end()){
		delete *it;
		it++;
	}
	bricks.clear();
}
void Model::resetBricks(){
	deleteBricks();
	makeBricks();	
}
Model::Model() :

  gamedata(Gamedata::getInstance()),
  io(IOManager::getInstance()),
  strategy(NULL),
  sound(SDLSound::getInstance()),
  clock(),
  screen( io->getScreen() ),
  factory(FrameFactory::getInstance()),
  mfactory(MultiFrameFactory::getInstance()),
  world(factory->getFrame("nebula","nebula")),
  player(new Player()),
  ball(new Ball(Vector2f(gamedata->getXmlInt("playerballPosX"),gamedata->getXmlInt("playerballPosY")),
			Vector2f(gamedata->getXmlInt("playerballVelX"),gamedata->getXmlInt("playerballVelY")),
			factory->getFrame("playerball","playerball"))),
  balls(),
  bricks(),
  items(),
  explosions(),
  score(),
  showHelp(false)
  {
    if (SDL_Init(SDL_INIT_VIDEO) != 0) {
      throw string("Unable to initializade SDL: ");
    }
    SDL_putenv(const_cast<char*>("SDL_VIDEO_CENTERED=center"));
    
    //collision strategy
     if ( gamedata->getXmlStr("CollisionStrategy") ==
       "RectangularCollisionStrategy") {
		strategy = new RectangularCollisionStrategy;
	 } else if ( gamedata->getXmlStr("CollisionStrategy") ==
        "MidPointCollisionStrategy") {
        strategy = new MidPointCollisionStrategy;
	 } else 
		strategy = new PerPixelCollisionStrategy;
    
    //sound
    //1
    sound->addChannel(gamedata->getXmlStr("hitFile")); 
    //2
    sound->addChannel(gamedata->getXmlStr("explosionFile")); 
    //3
    sound->addChannel(gamedata->getXmlStr("itemFile")); 
    
    //bricks
    makeBricks();
    srand((unsigned)time(0));
    atexit(SDL_Quit);
    SDL_WM_SetCaption( gamedata->getXmlStr("appName").c_str(), NULL );
}

void Model::draw() const {
  
  world.draw();

  drawBricks();
  drawItems();	
  drawExplosions();
  player->draw();
  drawBalls();
  ball->draw();
  
  
  
  
 if(showHelp) {
	drawHelp();
 }
  	drawFps();	
  	drawCount();
  	drawLife();
  	drawScore();
  SDL_Flip(screen);
}

void Model::update() {
  unsigned ticks = clock.getElapsedTicks();
  player->update(ticks);
  //ball update
  if(ball->isEmitted()){	  
	ball->update(ticks);
  } else {
	ball->update(player);
  }
  updateBalls(ticks);
  updateBricks(ticks);
  updateItems(ticks);
  updateExplosions(ticks);
  
  //collision detection
  if(ball->isEmitted()){	  
	playerCollision(player,ball);
  }
  ballsCollision();
  bricksCollision(ball);
  itemsCollision();
  //lost life
  if(ball->Y() > gamedata->getXmlInt("worldHeight"))
  {
	  //ball->setPosition(
	  //Vector2f(gamedata->getXmlInt("playerballPosX"),gamedata->getXmlInt("playerballPosY")));
	  //ball->setVelocity(ball->getInitVelocity());
	  //resetBricks();
	  //toggleHelp();
	  score.resetCombo();
	  deleteBalls();
	  player->lostLife();
	  ball->toggleEmitted();
	  ball->resetVelocity();
  }
  //next stage 
  if(bricks.size() == 0 && explosions.size() == 0)
  {
	  clock.pause();
  }
  //gamevoer
  if(player->getLife() <= 0 && !clock.isPaused()){
	  deleteBalls();
	  deleteItems();
	  deleteExplosions();
	 ball->toggleEmitted();
	 resetBricks();
	 clock.pause();
  }
  if(!clock.isPaused()){
  clock++;
  }
}

void Model::pause(){
	//std::cout<<"call pause"<<std::endl;
	if(!clock.isPaused()){
		clock.pause();
	} else {
		clock.unpause();
	}
	//std::cout<<clock.isPaused()<<std::endl;
}

bool Model::isPaused() const {
	return clock.isPaused();
}

void Model::drawHelp() const {
	SDL_Color color = {0,255,0,0};
	io->printMessageAt("Help memu", 200, 110,color);
	io->printMessageAt("1.Press <p> or <f1> to pause or resume the game", 200, 140,color);
	io->printMessageAt("2.Press <LEFT> or <RIGHT> to move the bar", 200, 170,color);
	io->printMessageAt("3.Press <m> to pause or resume the bgm", 200, 200,color);
	io->printMessageAt("4.Press <e> to explode the bricks", 200, 230,color);
	io->printMessageAt("5.Press <r> to reset the bricks", 200, 260,color);
	io->printMessageAt("6.Press <q> or <Esc> to quit", 200, 290,color);
}

void Model::drawFps() const {
	SDL_Color color = {255,255,0,0};
	//clock.debug();
	io->printMessageValueAt("Fps: ",clock.getFps(),gamedata->getXmlInt("worldWidth")*3/4, 10,color);

}

void Model::drawCount() const {
	SDL_Color color = {255,255,0,0};
	//clock.debug();
	io->printMessageValueAt("Bricks: ",bricks.size(),gamedata->getXmlInt("worldWidth")*3/4, 30,color);
	io->printMessageValueAt("Items: ",items.size(),gamedata->getXmlInt("worldWidth")*3/4, 50,color);
	std::stringstream ss;
	
	ss<<"Speed: (" << ball->velocityX()<<","<<ball->velocityY()<<")";
	std::string speed = ss.str();
	io->printMessageAt(speed,gamedata->getXmlInt("worldWidth")*3/4, 70,color);
}

void Model::drawLife() const {
	SDL_Color color = {0,0,0,0};
	//clock.debug();
	int life = player->getLife();
	io->printMessageAt("Lifes: ",10, 10,color);
	for(int i = 0 ; i < life ; ++i){
		io->printPicAt(gamedata->getXmlStr("lifeFile"),10,20*i+40);
	}
}

void Model::drawScore() const {
	SDL_Color color = {0,255,0,0};
	io->printMessageValueAt("Score: ",score.getScore(),gamedata->getXmlInt("worldWidth")*1/3, 10,color);
	io->printMessageValueAt("Combo: ",score.getCombo(),gamedata->getXmlInt("worldWidth")*1/3, 30,color);
}

void Model::drawBricks() const {
	std::list<Sprite*>::const_iterator it = bricks.begin();
	while(it != bricks.end()){
		(*it)->draw();
		it++;
	}
}

void Model::updateBricks(unsigned ticks){
		std::list<Sprite*>::iterator it = bricks.begin();
	while(it != bricks.end()){
		(*it)->update(ticks);
		it++;
	}
}

void Model::drawItems() const {
	std::list<Item*>::const_iterator it = items.begin();
	while(it != items.end()){
		(*it)->draw();
		it++;
	}
}

void Model::updateItems(unsigned ticks){
	std::list<Item*>::iterator it = items.begin();
	while(it != items.end()){
		(*it)->update(ticks);
		if((*it)->Y() > gamedata->getXmlInt("worldHeight")){
			delete *it;
			//cout<<"delete item"<<endl;
			it = items.erase(it);
		} else 
			it++;
	}
	//cout<<"items:"<<items.size()<<endl;
}

void Model::deleteItems()
{
	std::list<Item*>::iterator it = items.begin();
	while(it != items.end()){
		delete *it;
		it++;
	}
	items.clear();
}

void Model::generateItem(const Sprite & s){
	int base = 8 * (100.0 / gamedata->getXmlInt("itemPercentage"));
	int random = rand() % base;
			
	cout<<base<<" "<<random<<endl;
	if(random < 8)
	{
		//random = random % 2;
		//items.push_back(new Item(s,4));
		items.push_back(new Item(s,random));
	}
}

void Model::obtainItem(const Item& item){
				switch(item.getType()){
				case Item::SPEEDU:
					ball->setVelocity(ball->getVelocity() * 1.25);
					
					cout<<ball->velocityX()<<","<<ball->velocityY()<<endl;
					break;
				case Item::SPEEDD:
					ball->setVelocity(ball->getVelocity() * 0.8);
					
					cout<<ball->velocityX()<<","<<ball->velocityY()<<endl;
					break;
				case Item::LENGTHP:
					player->increaseLength();
					break;
				case Item::LENGTHM:
					player->decreaseLength();
					break;
				case Item::BALL:
					addBalls();
					break;
				case Item::LIFE:
					player->rewardLife();
					break;
				case Item::EXPLOSION:
					break;
				case Item::NEXT:
					break;
			}
}

//balls
void Model::addBalls(){
	Vector2f pos(ball->getPosition());
	Vector2f vel(ball->getVelocity());
	balls.push_back(new Ball(pos,vel+Vector2f(-50,50),factory->getFrame("redball","playerball")));
	balls.push_back(new Ball(pos,vel+Vector2f(50,-50),factory->getFrame("greenball","playerball")));
}

void Model::drawBalls() const{
	std::list<Ball*>::const_iterator it = balls.begin();
	while(it != balls.end()){
		(*it)->draw();
		it++;
	}
}

void Model::updateBalls(unsigned ticks){
	std::list<Ball*>::iterator it = balls.begin();
	while(it != balls.end()){
		(*it)->update(ticks);
		if((*it)->Y() > gamedata->getXmlInt("worldHeight")){
			delete *it;
			it = balls.erase(it);
		} else 
			it++;
	}
}

void Model::deleteBalls()
{
	std::list<Ball*>::iterator it = balls.begin();
	while(it != balls.end()){
		delete *it;
		it++;
	}
	balls.clear();
}


void Model::playSound(int index){
	sound->operator [](index-1);
}

void Model::toggleBgm(){
	sound->toggleMusic();
}

void Model::explode(){	
	if(bricks.size() > 0) {
		std::list<Sprite*>::iterator it = bricks.begin();
		int n = gamedata->getXmlInt("brickChunks");
		explosions.push_back(new ExplodingSprite(**it, n));
		playSound(2);
		//cout<<bricks.size()<<endl;
		delete *it;
		bricks.erase(it);
	}
}

void Model::drawExplosions() const {
	std::list<ExplodingSprite*>::const_iterator it = explosions.begin();
	while(it != explosions.end()){
		(*it)->draw();
		it++;
	}
}

void Model::updateExplosions(unsigned ticks){
		std::list<ExplodingSprite*>::iterator it = explosions.begin();
	    while(it != explosions.end()){
		(*it)->update(ticks);
		if((*it)->chunkCount() == 0){
			delete *it;
			it = explosions.erase(it);
		} else it++;
	}
}

void Model::deleteExplosions(){
	std::list<ExplodingSprite*>::iterator it = explosions.begin();
	while(it != explosions.end()){
		delete *it;
		it++;
	}
	explosions.clear();
}

void Model::playerCollision(Player* p,Ball* b){
	if ( !strategy->execute(*p, *b) ){
		b->setCollided(false);
		return;
	}
		cout<<"Collision"<<endl;
		//score combo reset
		score.resetCombo();
	if(!b->isCollided()){
		playSound(1);
		b->setCollided(true);
		float pleft = p->X();
	    float bleft = b->X();
	    float pright = pleft+p->getFrame()->getWidth();
	    float bright = bleft+b->getFrame()->getWidth();
	    if(bleft >= pleft && bright <= pright){
		  b->velocityY(-b->velocityY());
	    } else { 
		  b->velocityY(-b->velocityY());
		  b->velocityX(-b->velocityX());
	    }
  }
}

void Model::bricksCollision(Ball* b){
	std::list<Sprite*>::iterator it = bricks.begin();
	float bleft;
	float brleft;
	float bright;
	float brright;
	while(it != bricks.end()){
		if(strategy->execute(*b, **it))
		{
			bleft = b->X();
			brleft = (*it)->X();
			bright = bleft+b->getFrame()->getWidth();
			brright = brleft+(*it)->getFrame()->getWidth();
			if(bleft >= brleft && bright <= brright){
				b->velocityY(-b->velocityY());
			} else { 
				b->velocityY(-b->velocityY());
				b->velocityX(-b->velocityX());
			}
			int n = gamedata->getXmlInt("brickChunks");
			explosions.push_back(new ExplodingSprite(**it, n));
			
			//generate items
			
			generateItem(**it);
			//end
			playSound(2);
			delete *it;
			bricks.erase(it);
			//score plus
			score.plusCombo(100);
			return;
		}
		it++;
	}
}

void Model::ballsCollision(){
	std::list<Ball*>::iterator it = balls.begin();
	while(it != balls.end()){
		playerCollision(player,*it);
		bricksCollision(*it);
		it++;
	}
}

void Model::itemsCollision(){
	std::list<Item*>::iterator it = items.begin();
	while(it != items.end()){
		if(strategy->execute(*player, **it))
		{
			playSound(3);
			//obtain item
			obtainItem(**it);
			delete *it;
			items.erase(it);
			//score plus
			score.plus(500);
			return;
		}
		else it++;
	}
}

void Model::emitBall(){
	if(!ball->isEmitted()){
		if((ball->X() < gamedata->getXmlInt("worldWidth")/2 && ball->velocityX() > 0)
			||(ball->X() > gamedata->getXmlInt("worldWidth")/2 && ball->velocityX() < 0))
		{
			ball->velocityX(-ball->velocityX());
		}
		ball->toggleEmitted();
	}
}

void Model::start(){
	clock.start();
}

